#include <riscv/inst.h>
#include <riscv/regs.h>

#define GET_STR get_str

#define INST_STR(INST, STR, RD, SRC1, SRC2) \
  std::string INST::GET_STR() { \
    return std::string(STR) + " " + Regs::get_reg_name(this->RD) \
      + ", " + Regs::get_reg_name(this->SRC1) + ", " \
      + Regs::get_reg_name(this->SRC2); \
  }

#define INST_STR1(INST, STR, RD, SRC) \
  std::string INST::GET_STR() { \
    return std::string(STR) + " " + Regs::get_reg_name(this->RD) \
      + ", " + Regs::get_reg_name(this->SRC); \
  }

#define INST_STR_I(INST, STR, RD, SRC, IMM) \
  std::string INST::GET_STR() { \
    return std::string(STR) + " " + Regs::get_reg_name(this->RD) \
      + ", " + Regs::get_reg_name(this->SRC) + ", " \
      + std::to_string(this->IMM); \
  }

#define INST_STR_OFF(INST, STR, RD, OFF, SRC) \
  std::string INST::GET_STR() { \
    return std::string(STR) + " " + Regs::get_reg_name(this->RD) \
      + ", " + std::to_string(OFF) + "(" \
      + Regs::get_reg_name(this->SRC) + ")"; \
  }

#define INST_STR_I1(INST, STR, REG, IMM) \
  std::string INST::GET_STR() { \
    return std::string(STR) + " " + Regs::get_reg_name(this->REG) \
      + ", " + std::to_string(this->IMM); \
  }

#define INST_STR_NOTHING(INST, STR) \
  std::string INST::GET_STR() { \
    return STR; \
  }

namespace riscv {

  Inst::~Inst() {}

  INST_STR_I(InstAddi, "addi", rd, src, imm);

  INST_STR(InstAdd, "add", rd, src1, src2);
  INST_STR(InstSub, "sub", rd, src1, src2);
  INST_STR(InstMul, "mul", rd, src1, src2);
  INST_STR(InstDiv, "div", rd, src1, src2);

  INST_STR(InstAddw, "addw", rd, src1, src2);
  INST_STR(InstSubw, "subw", rd, src1, src2);
  INST_STR(InstMulw, "mulw", rd, src1, src2);
  INST_STR(InstDivw, "divw", rd, src1, src2);

  INST_STR_OFF(InstSd, "sd", rd, off, src);
  INST_STR_OFF(InstSw, "sw", rd, off, src);
  INST_STR_OFF(InstLd, "ld", rd, off, src);
  INST_STR_OFF(InstLw, "lw", rd, off, src);

  std::string InstJ::GET_STR() {
    return std::string("j ") + this->block->get_name();
  }

  std::string InstBne::GET_STR() {
    return std::string("bne ") + Regs::get_reg_name(this->src1)
      + ", " + Regs::get_reg_name(this->src2) + ", "
      + this->block->get_name();
  }

  std::string InstBeq::GET_STR() {
    return std::string("beq ") + Regs::get_reg_name(this->src1)
      + ", " + Regs::get_reg_name(this->src2) + ", "
      + this->block->get_name();
  }

  INST_STR_I1(InstLi, "li", reg, imm);
  INST_STR1(InstMv, "mv", rd, src);

  std::string InstCall::GET_STR() {
    return std::string("call ") + this->func->name;
  }

  INST_STR_NOTHING(InstRet, "ret");

  std::string InstLla::GET_STR() {
    std::string ret = std::string("lla ") + Regs::get_reg_name(this->rd)
      + ", ";
    if(auto * global = std::get_if<Global*>(&this->global_float)) {
      ret += (*global)->get_name();
    } else if(auto * float_number = std::get_if<FloatNumber*>(&this->global_float)) {
      ret += (*float_number)->get_name();
    } else {
      Panic("lla undfined globa float");
    }
    return ret;
  }

  std::string InstSextW::GET_STR() {
    return std::string("sext.w ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFaddD::GET_STR() {
    return std::string("fadd.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src1)
      + ", " + Regs::get_reg_name(this->src2);
  }

  std::string InstFsubD::GET_STR() {
    return std::string("fsub.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src1)
      + ", " + Regs::get_reg_name(this->src2);
  }

  std::string InstFmulD::GET_STR() {
    return std::string("fmul.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src1)
      + ", " + Regs::get_reg_name(this->src2);
  }

  std::string InstFdivD::GET_STR() {
    return std::string("fdiv.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src1)
      + ", " + Regs::get_reg_name(this->src2);
  }

  std::string InstFsd::GET_STR() {
    return std::string("fsd ") + Regs::get_reg_name(this->rd)
      + ", " + std::to_string(this->off)
      + "(" + Regs::get_reg_name(this->src) + ")";
  }

  std::string InstFsw::GET_STR() {
    return std::string("fsw ") + Regs::get_reg_name(this->rd)
      + ", " + std::to_string(this->off)
      + "(" + Regs::get_reg_name(this->src) + ")";
  }

  std::string InstFld::GET_STR() {
    return std::string("fld ") + Regs::get_reg_name(this->rd)
      + ", " + std::to_string(this->off)
      + "(" + Regs::get_reg_name(this->src) + ")";
  }

  std::string InstFlw::GET_STR() {
    return std::string("flw ") + Regs::get_reg_name(this->rd)
      + ", " + std::to_string(this->off)
      + "(" + Regs::get_reg_name(this->src) + ")";
  }

  std::string InstFmvS::GET_STR() {
    return std::string("fmv.s ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFmvD::GET_STR() {
    return std::string("fmv.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFmvDX::GET_STR() {
    return std::string("fmv.d.x ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFmvXD::GET_STR() {
    return std::string("fmv.x.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFcvtDS::GET_STR() {
    return std::string("fcvt.d.s ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFcvtSD::GET_STR() {
    return std::string("fcvt.s.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src);
  }

  std::string InstFcvtLD::GET_STR() {
    return std::string("fcvt.l.d ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src)
      + ", " + enum_name(this->rm);
  }

  std::string InstFcvtDL::GET_STR() {
    return std::string("fcvt.d.l ") + Regs::get_reg_name(this->rd)
      + ", " + Regs::get_reg_name(this->src)
      + ", " + enum_name(this->rm);
  }
}
